/*
 *    Copyright (c) 2018-2025, lengleng All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * Neither the name of the pig4cloud.com developer nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * Author: lengleng (wangiegie@gmail.com)
 */
package com.pig4cloud.pig.mt.service.impl;

import cn.binarywang.wx.miniapp.api.WxMaService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pig4cloud.pig.mt.common.api.utils.Query;
import com.pig4cloud.pig.mt.config.TaskExcutor;
import com.pig4cloud.pig.mt.config.WxMaConfiguration;
import com.pig4cloud.pig.mt.entity.WxUser;
import com.pig4cloud.pig.mt.mapper.WxUserMapper;
import com.pig4cloud.pig.mt.service.WxUserService;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.WxMpUserService;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import me.chanjar.weixin.mp.bean.result.WxMpUserList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 用户表
 *
 * @author JLM
 * @date 2021-03-05 11:45:09
 */
@Slf4j
@Service
public class WxUserServiceImpl extends ServiceImpl<WxUserMapper, WxUser> implements WxUserService {
	@Autowired
	private WxUserMapper userMapper;

	@Autowired
	private WxMpService wxService;

//	@Autowired
//	private WxMaService maService;

	private volatile static boolean syncWxUserTaskRunning = false;

	@Override
	public IPage<WxUser> queryPage(Map<String, Object> params) {
		String openid = (String) params.get("openid");
		String nickname = (String) params.get("nickname");
		String appid = (String) params.get("appid");
		String city = (String) params.get("city");
		String qrSceneStr = (String) params.get("qrSceneStr");
		return this.page(
				new Query<WxUser>().getPage(params),
				new QueryWrapper<WxUser>()
						.eq(!StringUtils.isEmpty(appid), "appid", appid)
						.eq(!StringUtils.isEmpty(openid), "openid", openid)
						.like(!StringUtils.isEmpty(nickname), "nickname", nickname)
						.eq(!StringUtils.isEmpty(city), "city", city)
						.eq(!StringUtils.isEmpty(qrSceneStr), "qrSceneStr", qrSceneStr)
		);
	}

	/**
	 * 根据openid更新用户信息
	 *
	 * @param openid
	 * @return
	 */
	@Override
	public WxUser refreshUserInfo(String openid, String appid) {
		try {

//			final WxMaService wxService = WxMaConfiguration.getMaService(appid);
			// 获取微信用户基本信息
			log.info("更新用户信息，openid={}", openid);
			wxService.switchover(appid);
			WxMpUser userWxInfo = wxService.getUserService().userInfo(openid, null);
			if (userWxInfo == null) {
				log.error("获取不到用户信息，无法更新,openid:{}", openid);
				return null;
			}
			WxUser user = new WxUser(userWxInfo, appid);
			this.saveOrUpdate(user);
			return user;
		} catch (Exception e) {
			log.error("更新用户信息失败,openid:{}", openid);
		}
		return null;
	}

	/**
	 * 异步批量同步用户信息
	 *
	 * @param openidList
	 */
	@Override
	@Async
	public void refreshUserInfoAsync(String[] openidList, String appid) {
		log.info("批量更新用户信息：任务开始");
		for (String openid : openidList) {
			wxService.switchover(appid);
			TaskExcutor.submit(() -> this.refreshUserInfo(openid, appid));
		}
		log.info("批量更新用户信息：任务全部添加到线程池");
	}

	/**
	 * 数据存在时更新，否则新增
	 *
	 * @param user
	 */
	@Override
	public void updateOrInsert(WxUser user) {
		int updateCount = userMapper.updateById(user);
		if (updateCount < 1) {
			userMapper.insert(user);
		}
	}

	@Override
	public void unsubscribe(String openid) {
		userMapper.unsubscribe(openid);
	}

	/**
	 * 同步用户列表,公众号一次拉取调用最多拉取10000个关注者的OpenID，可以通过传入nextOpenid参数多次拉取
	 */
	@Override
	@Async
	public void syncWxUsers(String appid) {
		if (syncWxUserTaskRunning) {
			return;//同步较慢，防止个多线程重复执行同步任务
		}
		syncWxUserTaskRunning = true;
		log.info("同步公众号粉丝列表：任务开始");
		wxService.switchover(appid);
		boolean hasMore = true;
		String nextOpenid = null;
		WxMpUserService wxMpUserService = wxService.getUserService();
		try {
			int page = 1;
			while (hasMore) {
				WxMpUserList wxMpUserList = wxMpUserService.userList(nextOpenid);//拉取openid列表，每次最多1万个
				log.info("拉取openid列表：第{}页，数量：{}", page++, wxMpUserList.getCount());
				List<String> openids = wxMpUserList.getOpenids();
				this.syncWxUsers(openids, appid);
				nextOpenid = wxMpUserList.getNextOpenid();
				hasMore = !StringUtils.isEmpty(nextOpenid) && wxMpUserList.getCount() >= 10000;
			}
		} catch (WxErrorException e) {
			log.error("同步公众号粉丝出错:", e);
		}
		log.info("同步公众号粉丝列表：完成");
		syncWxUserTaskRunning = false;
	}

	/**
	 * 通过传入的openid列表，同步用户列表
	 *
	 * @param openids
	 */
	@Override
	public void syncWxUsers(List<String> openids, String appid) {
		if (openids.size() < 1) {
			return;
		}
		final String batch = openids.get(0).substring(20);//截取首个openid的一部分做批次号（打印日志时使用，无实际意义）
		WxMpUserService wxMpUserService = wxService.getUserService();
		int start = 0, batchSize = openids.size(), end = Math.min(100, batchSize);
		log.info("开始处理批次：{}，批次数量：{}", batch, batchSize);
		while (start < end && end <= batchSize) {//分批处理,每次最多拉取100个用户信息
			final int finalStart = start, finalEnd = end;
			final List<String> subOpenids = openids.subList(finalStart, finalEnd);
			TaskExcutor.submit(() -> {//使用线程池同步数据，否则大量粉丝数据需同步时会很慢
				log.info("同步批次:【{}--{}-{}】，数量：{}", batch, finalStart, finalEnd, subOpenids.size());
				wxService.switchover(appid);
				List<WxMpUser> wxMpUsers = null;//批量获取用户信息，每次最多100个
				try {
					wxMpUsers = wxMpUserService.userInfoList(subOpenids);
				} catch (WxErrorException e) {
					log.error("同步出错，批次：【{}--{}-{}】，错误信息：{}", batch, finalStart, finalEnd, e);
				}
				if (wxMpUsers != null && !wxMpUsers.isEmpty()) {
					List<WxUser> wxUsers = wxMpUsers.parallelStream().map(item -> new WxUser(item, appid)).collect(Collectors.toList());
					this.saveOrUpdateBatch(wxUsers);
				}
			});
			start = end;
			end = Math.min(end + 100, openids.size());
		}
		log.info("批次：{}处理完成", batch);
	}
}
